package pl.edu.mimuw.irs.core.model.analysis.dd;

import java.io.Serializable;
import java.util.LinkedHashSet;
import java.util.Random;
import java.util.Set;
import java.util.Map.Entry;

import pl.edu.mimuw.irs.core.model.analysis.Time;
import pl.edu.mimuw.irs.core.model.analysis.exception.InternalAnalysisException;
import pl.edu.mimuw.irs.core.model.analysis.exception.InternalAnalysisException.AnalysisCause;
import pl.edu.mimuw.irs.core.model.analysis.resources.RequirementPlace;
import pl.edu.mimuw.irs.core.model.analysis.resources.ResourcePlace;
import pl.edu.mimuw.irs.core.model.design.resource.Role;
import pl.edu.mimuw.irs.core.model.design.resource.RoleInstance;

public class TimeStrategy implements Serializable {

	private static final long serialVersionUID = 5647225988512837278L;

	private static Random random = new Random();
	
	// *** Attributes

	private ActivityWrapper activityWrapper;
	private Type strategy;
	private TimeStrategy parentStrategy;
	private double maxModifier;
	private double minModifier;
	private int modifiersCount;
	private double modifiersSum;
	private Time timeTaken;
	
	public double getModifier() {
		switch (strategy) {
		case OPTIMISTIC:
			return minModifier;
		case PESIMISTIC:
			return maxModifier;
		default:
			if (modifiersCount > 0) return modifiersSum / modifiersCount;
			else return 0;
		}
	}
	
	public Time getTimeTaken() {
		if (timeTaken == null) {
			double exactTimeTaken = activityWrapper.getActivity().getTimeUnits() * getModifier();
			/* Standard deviation */
			timeTaken = new Time(exactTimeTaken + (random.nextGaussian() * activityWrapper.getActivity().getDeviation()));
		}
		return timeTaken;
	}
	
	// *** Constructors
	
	public TimeStrategy(ActivityWrapper activityWrapper, Type strategy) {
		this.activityWrapper = activityWrapper;
		this.strategy = strategy;
		if (activityWrapper.getAscendingPath() != null) {
			parentStrategy = activityWrapper.getAscendingPath().getRoot().getOwner().getTimeStrategy();
		} else {
			parentStrategy = null;
		}
		initModifiers();
	}
	
	// *** Methods
	
	private void initModifiers() {
		Set<RoleInstance> usedRoles = 
			getUsedRolesInstances();

		maxModifier = 0;
		minModifier = Double.MAX_VALUE;
		modifiersCount = 0;
		modifiersSum = 0;
		
		for (RoleInstance instance : usedRoles) {
			double roleModifier;
			if (instance.getCalculatedModifier() != null) roleModifier = instance.getCalculatedModifier();
			else roleModifier = instance.getModifier();
			if (maxModifier < roleModifier) maxModifier = roleModifier;
			if (minModifier > roleModifier) minModifier = roleModifier;
			modifiersCount++;
			modifiersSum += roleModifier;
		}
		
		if (parentStrategy != null) {
			if (maxModifier < parentStrategy.maxModifier) maxModifier = parentStrategy.maxModifier;
			if (minModifier > parentStrategy.minModifier) minModifier = parentStrategy.minModifier;
			modifiersCount += parentStrategy.modifiersCount;
			modifiersSum += parentStrategy.modifiersSum;
		}
	}
	
	private Set<RoleInstance> getUsedRolesInstances() {
		Set<RoleInstance> usedRoles = new LinkedHashSet<RoleInstance>();
		
		/* We get matches of resources roles to required roles */
		for (Entry<ResourcePlace, Set<RequirementPlace>> entry : 
				activityWrapper.getInvolvedResources().entrySet()) {
			/* Each Resource is bound to one or more requirements */
			for (RequirementPlace requirement : entry.getValue()) {
				/* Having requirements linked to resource we know what roles are used */
				for (Role role : requirement.getRequirement().getRolesMap().keySet()) {
					/* Each role from requirement must be in resource - otherwise it means error */
					RoleInstance instance = entry.getKey().getResource().getRolesMap().get(role);
					if (instance == null) 
						throw new InternalAnalysisException(AnalysisCause.RESOURCE_BOUND_TO_UNMET_REQUIREMENT, 
								entry.getKey(), entry.getValue(), role);
					usedRoles.add(instance);
				}
			}
		}
		
		return usedRoles;
	}
	
	
	// *** Inner enums
	
	public enum Type {
		PESIMISTIC,
		OPTIMISTIC,
		AVERAGE;
	}
}
